﻿/* *****************************************************************************
* Creator: Rastko Šoškić - rssole@hotmail.com
* Date: 07/29/2009
* Description: ImageLoadingManager control
*
*
* ****************************************************************************** */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Json;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Rastko.OrderedImageLoading.Controls
{
    /// <summary>
    /// This is crucial control for ordered loading of images on page.
    /// It manages ordered loading of images on page.
    /// </summary>
    [DefaultProperty("DefaultImageWidth")]
    [ToolboxData("<{0}:ImageLoadingManager runat=server></{0}:ImageLoadingManager>")]
    [NonVisualControl]
    [Designer(typeof(Design.ImageLoadingManagerDesigner))]
    public class ImageLoadingManager : Control, IScriptControl
    {
        #region Fields

        private List<OrderedImageInfo> imagesToManage; 

        #endregion

        #region Ctor

        public ImageLoadingManager()
        {
            imagesToManage = new List<OrderedImageInfo>();
        } 

        #endregion

        #region Overrides

        protected override void OnInit(EventArgs e)
        {
            // Here, essentially, I am verifying that there is only one 
            // instance of control on page, just like ScriptManager or 
            // WebPartManager controls. I've used approach as on
            // http://iridescence.no/post/EnsuringaSingleInstanceofaControlonaPage.aspx

            base.OnInit(e);
            Page controlParentPage = Page;
            if (controlParentPage != null)
            {
                // Check if instance of control has already been added to the page
                if (controlParentPage.Items.Contains(typeof(ImageLoadingManager)))
                {
                    throw new InvalidOperationException("Only one instance of a ImageLoadingManger can be added to the page.");
                }

                controlParentPage.Items.Add(typeof(ImageLoadingManager), this);
            }
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            var currentScriptManager = ScriptManager.GetCurrent(Page);

            if (currentScriptManager != null)
            {
                currentScriptManager.RegisterScriptControl(this);
            }
            else
            {
                throw new InvalidOperationException("A ScriptManager must be present in the page.");
            }
        }

        protected override void Render(HtmlTextWriter writer)
        {
            base.Render(writer);

            // Register script descriptors with ScriptManager
            var currentScriptManager = ScriptManager.GetCurrent(Page);
            if (currentScriptManager != null)
            {
                currentScriptManager.RegisterScriptDescriptors(this);
            }
        } 

        #endregion

        #region Public methods

        /// <summary>
        /// Gets the current ImageLoadingManager instance on a given page
        /// </summary>
        /// <param name="aPage">The page which ImageLoadingManager instance is retrieved from</param>
        /// <returns>The ImageLoadingManager control</returns>
        /// <exception cref="InvalidOperationException">If no ImageLoadingManager control exists on the page</exception>
        public static ImageLoadingManager GetCurrent(Page aPage)
        {
            if (aPage == null)
            {
                throw new ArgumentNullException("aPage");
            }

            // Get the manager - remember we added it to the Items collection in OnInit
            var imgLoadingManager = aPage.Items[typeof(ImageLoadingManager)] as ImageLoadingManager;

            // No manager on page, crash and burn :)
            if (imgLoadingManager == null)
            {
                throw new InvalidOperationException("Could not find a ImageLoadingManager control on the page.");
            }

            return imgLoadingManager;
        }

        /// <summary>
        /// Registers OrderedImage control with this instance
        /// of ImageLoadingManager
        /// </summary>
        /// <remarks>Registration is necessary as ImageLoadingManager
        /// has "to know" which images' loading it should manage</remarks>
        /// <param name="image">Particular OrderedImage control to be registered</param>
        public void RegisterOrderedImage(OrderedImage image)
        {
            // First we have to register actually requested image url plus several other properties
            // such as client id, loading order etc
            OrderedImageInfo oii = new OrderedImageInfo
                                       {
                                           ClientID = image.ClientID,
                                           ImageUrl = ResolveClientUrl(image.ImageUrl),
                                           LoadingOrder = image.LoadingOrder,
                                           Width = image.Width,
                                           Height = image.Height
                                       };
            imagesToManage.Add(oii);
            // Replacing actual url with "progress indicator"
            image.ImageUrl = Page.ClientScript.GetWebResourceUrl(GetType(),
                Assembly.GetExecutingAssembly().GetName().Name + ".Resources.ajax_progress16.gif");

            // Setting width and height to size of progress indicator
            // if width or height of image are explicitly set...
            // TODO: Later this should be made customizable
            if (!image.Width.IsEmpty) image.Width = Unit.Pixel(16);
            if (!image.Height.IsEmpty) image.Height = Unit.Pixel(16);
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Serializes list of appropriate data structures into JSON string
        /// </summary>
        /// <returns>Serialized</returns>
        private string GetListOfImagesAsJSONString()
        {
            DataContractJsonSerializer jsonSerializer = 
                new DataContractJsonSerializer(typeof(IEnumerable<OrderedImageInfo>));
            
            MemoryStream msTemp = new MemoryStream();
            jsonSerializer.WriteObject(msTemp, imagesToManage.OrderBy(oii => oii.LoadingOrder)); // serialize list
            // Notice that list is ordered prior to serialization, 
            // that will make our lives somewhat easier on client side :)

            StreamReader jsonReader = new StreamReader(msTemp);
            msTemp.Seek(0, SeekOrigin.Begin); // set stream position back to the beginning of the stream
            return jsonReader.ReadToEnd(); // read JSON string (and return it) :)
        }

        #endregion

        #region Implementation of IScriptControl

        /// <summary>
        /// Gets a collection of script descriptors that represent ECMAScript (JavaScript) client components.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerable"/> collection of <see cref="T:System.Web.UI.ScriptDescriptor"/> objects.
        /// </returns>
        public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
        {
            ScriptComponentDescriptor scd = 
                new ScriptComponentDescriptor("Rastko.OrderedImageLoading.Controls.ImageLoadingManager");

            string listOfImagesAsJSONString = GetListOfImagesAsJSONString();
            scd.AddScriptProperty("listOfImages", listOfImagesAsJSONString);

            yield return scd;
        }
        
        /// <summary>
        /// Gets a collection of <see cref="T:System.Web.UI.ScriptReference"/> objects that define script resources that the control requires.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerable"/> collection of <see cref="T:System.Web.UI.ScriptReference"/> objects.
        /// </returns>
        public IEnumerable<ScriptReference> GetScriptReferences()
        {
            string assemblyName = Assembly.GetExecutingAssembly().GetName().Name;
            
            ScriptReference jQuery = new ScriptReference(
                assemblyName + ".Resources.jquery-1.3.2.js", assemblyName);
            yield return jQuery;

            ScriptReference ilm = new ScriptReference(
                assemblyName + ".ImageLoadingManager.js", assemblyName);
            yield return ilm;
        }

        #endregion
    }
}